// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DataManager.cs" company="Concept Architecture">
//   (c) 2010 Muhammad Umer Farooq
// </copyright>
// <summary>
//   Defines the <seealso cref="DataManager" /> type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Hisaab.Business
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Runtime.Serialization;
    using Client.Common;
    using Common.Wrappers;
    using Entities;
    using Interfaces;
    using Microsoft.Practices.ServiceLocation;
    using Parser;

    /// <summary>
    /// Defines the <seealso cref="DataManager"/> type.
    /// </summary>
    public class DataManager : IDataManager
    {
        /// <summary>
        /// Member variable _accountManager
        /// </summary>
        private readonly IAccountManager _accountManager;

        /// <summary>
        /// Member variable _transactionManager
        /// </summary>
        private readonly ITransactionManager _transactionManager;

        /// <summary>
        /// Member variable _tagManager
        /// </summary>
        private readonly ITagManager _tagManager;

        /// <summary>
        /// Member variable _fileWrapper
        /// </summary>
        private readonly IFileWrapper _fileWrapper;

        /// <summary>
        /// Initializes a new instance of the <see cref="DataManager"/> class.
        /// </summary>
        public DataManager()
            : this(
            ServiceLocator.Current.GetInstance<IAccountManager>(),
            ServiceLocator.Current.GetInstance<ITransactionManager>(),
            ServiceLocator.Current.GetInstance<ITagManager>(),
            ServiceLocator.Current.GetInstance<IFileWrapper>())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataManager"/> class.
        /// </summary>
        /// <param name="accountManager">The account manager.</param>
        /// <param name="transactionManager">The transaction manager.</param>
        /// <param name="tagManager">The tag manager</param>
        /// <param name="fileWrapper">File wrapper</param>
        public DataManager(IAccountManager accountManager, ITransactionManager transactionManager, ITagManager tagManager, IFileWrapper fileWrapper)
        {
            Contract.Requires<ArgumentNullException>(accountManager != null);
            Contract.Requires<ArgumentNullException>(transactionManager != null);
            Contract.Requires<ArgumentNullException>(tagManager != null);
            Contract.Requires<ArgumentNullException>(fileWrapper != null);

            _accountManager = accountManager;
            _transactionManager = transactionManager;
            _tagManager = tagManager;
            _fileWrapper = fileWrapper;
        }

        /// <summary>
        /// Imports the data.
        /// </summary>
        /// <param name="importFileName">Name of the import file.</param>
        /// <param name="userId">The user id.</param>
        /// <param name="password">The password.</param>
        public void ImportData(string importFileName, int userId, string password)
        {
            var importData = ReadImportFile(importFileName, password);
            if (importData != null)
            {
                EraseExistingData(userId);
                InsertImportedData(importData, userId);
            }
        }

        /// <summary>
        /// Exports the data.
        /// </summary>
        /// <param name="exportFileName">Name of the import file.</param>
        /// <param name="userId">The user id.</param>
        /// <param name="password">The password.</param>
        public void ExportData(string exportFileName, int userId, string password)
        {
            if (string.IsNullOrWhiteSpace(exportFileName))
            {
                throw new ArgumentNullException("exportFileName");
            }

            var exportData = ExportToBackupData(userId);

            WriteExportFile(exportFileName, exportData, password);
        }

        /// <summary>
        /// Imports the bank data.
        /// </summary>
        /// <param name="fileName">The data contents.</param>
        /// <param name="banks">The banks.</param>
        /// <returns>
        /// Transactions parsed
        /// </returns>
        public IEnumerable<Transaction> ImportBankData(string fileName, Banks banks)
        {
            var dataReader = BankDataImporterFactory.CreateParser(banks);
            return dataReader.ImportCsv(fileName);
        }

        /// <summary>
        /// Exports to backup data.
        /// </summary>
        /// <param name="userId">The user id.</param>
        /// <returns>Backup data object</returns>
        public BackupData ExportToBackupData(int userId)
        {
            var exportData = new BackupData();

            var tags = _tagManager.List(new Tag { UserId = userId });

            foreach (var tag in tags)
            {
                exportData.Tags.Add(tag);
            }

            var accounts = _accountManager.List(new Account { UserId = userId });

            foreach (var account in accounts)
            {
                var exportAccount = new AccountEx(account);
                var transactions = _transactionManager.List(new Transaction { Account = account.AccountId });
                foreach (var transaction in transactions)
                {
                    exportAccount.Transactions.Add(transaction);
                }

                exportData.Accounts.Add(exportAccount);
            }

            return exportData;
        }

        /// <summary>
        /// Writes the export file.
        /// </summary>
        /// <param name="exportFileName">Name of the import file.</param>
        /// <param name="exportData">The export data.</param>
        /// <param name="password">The password.</param>
        public void WriteExportFile(string exportFileName, BackupData exportData, string password)
        {
            if (_fileWrapper.Exists(exportFileName))
            {
                _fileWrapper.Delete(exportFileName);
            }

            using (var fileStream = _fileWrapper.OpenWrite(exportFileName))
            {
                var serializer = new NetDataContractSerializer();
                serializer.Serialize(fileStream, exportData);
            }
        }

        /// <summary>
        /// Reads the import file.
        /// </summary>
        /// <param name="importFileName">Name of the import file.</param>
        /// <param name="password">The password.</param>
        /// <returns>Backup data read</returns>
        public BackupData ReadImportFile(string importFileName, string password)
        {
            BackupData importData;

            using (var fileStream = _fileWrapper.OpenRead(importFileName))
            {
                var serializer = new NetDataContractSerializer();
                importData = (BackupData)serializer.Deserialize(fileStream);
            }

            return importData;
        }

        /// <summary>
        /// Inserts the imported data.
        /// </summary>
        /// <param name="importData">The import data.</param>
        /// <param name="userId">The user id.</param>
        private void InsertImportedData(BackupData importData, int userId)
        {
            var tagMap = new Dictionary<int, int>();
            var accountMap = new Dictionary<int, int>();
            var transactionMap = new Dictionary<int, int>();
            var linkedTransactionMap = new Dictionary<int, int>();

            foreach (var tag in importData.Tags)
            {
                var oldId = tag.TagId;
                tag.TagId = 0;
                tag.UserId = userId;

                var newId = _tagManager.Add(tag);
                tagMap.Add(oldId, newId);
            }

            foreach (AccountEx account in importData.Accounts)
            {
                var oldId = account.AccountId;
                account.AccountId = 0;
                account.UserId = userId;

                var newId = _accountManager.Add(account);
                accountMap.Add(oldId, newId);

                foreach (var transaction in account.Transactions)
                {
                    var oldTransactionId = transaction.TransactionId;
                    transaction.TransactionId = 0;
                    transaction.Account = newId;
                    transaction.IsTransferTransaction = false;

                    if (transaction.RelatedTransactionId.HasValue && transaction.RelatedTransactionId.Value > 0)
                    {
                        linkedTransactionMap.Add(oldTransactionId, transaction.RelatedTransactionId.Value);
                    }

                    foreach (var tag in transaction.Tags)
                    {
                        tag.TagId = tagMap[tag.TagId];
                    }

                    var newTransactionId = _transactionManager.Add(transaction);
                    transactionMap.Add(oldTransactionId, newTransactionId);
                }
            }

            foreach (var link in linkedTransactionMap)
            {
                _transactionManager.RelateTransactions(transactionMap[link.Key], transactionMap[link.Value]);
            }
        }

        /// <summary>
        /// Erases the existing data.
        /// </summary>
        /// <param name="userId">The user id.</param>
        private void EraseExistingData(int userId)
        {
            var accounts = _accountManager.List(new Account { UserId = userId });
            foreach (var account in accounts)
            {
                _accountManager.Delete(account);
            }

            var tags = _tagManager.List(new Tag { UserId = userId });
            foreach (var tag in tags)
            {
                _tagManager.Delete(tag);
            }
        }

        /// <summary>
        /// Defines the <seealso cref="Hisaab.Business.DataManager.BackupData"/> type.
        /// </summary>
        [Serializable]
        public class BackupData
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="Hisaab.Business.DataManager.BackupData"/> class.
            /// </summary>
            public BackupData()
            {
                Accounts = new List<Account>();
                Tags = new List<Tag>();
            }

            /// <summary>
            /// Gets the accounts.
            /// </summary>
            /// <value>The accounts. </value>
            public ICollection<Account> Accounts { get; private set; }

            /// <summary>
            /// Gets the tags.
            /// </summary>
            /// <value>The tags. </value>
            public ICollection<Tag> Tags { get; private set; }
        }

        /// <summary>
        /// Defines the <seealso cref="AccountEx"/> type.
        /// </summary>
        [Serializable]
        private class AccountEx : Account
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="AccountEx"/> class.
            /// </summary>
            /// <param name="account">The account.</param>
            public AccountEx(Account account)
            {
                AccountId = account.AccountId;
                AccountType = account.AccountType;
                Name = account.Name;
                Transactions = new List<Transaction>();
            }

            /// <summary>
            /// Gets the transactions.
            /// </summary>
            /// <value>
            /// The transactions.
            /// </value>
            public ICollection<Transaction> Transactions { get; private set; }
        }
    }
}